<template>
  <teleport :to="getContainer()">
    <div ref="modalWrapRef" class="draggable-modal" :class="{ fullscreen: fullscreenModel }">
      <Modal v-bind="omit(props, ['visible', 'onCancel', 'onOk', 'onUpdate:visible'])" v-model:visible="visibleModel"
        :mask-closable="false" :get-container="() => modalWrapRef" :width="innerWidth || width" @ok="emit('ok')"
        @cancel="emit('cancel')">
        <template #title>
          <slot name="title">{{ $attrs.title || '标题' }}</slot>
        </template>
        <template #closeIcon>
          <slot name="closeIcon">
            <Space class="ant-modal-operate" @click.stop>
              <fullscreen-outlined v-if="!fullscreenModel" @click="fullscreenModel = true" />
              <fullscreen-exit-outlined v-else @click="restore" />
              <close-outlined @click="closeModal" />
            </Space>
          </slot>
        </template>
        <slot>
          ① 窗口可以拖动；<br />
          ② 窗口可以通过八个方向改变大小；<br />
          ③ 窗口可以最小化、最大化、还原、关闭；<br />
          ④ 限制窗口最小宽度/高度。
        </slot>
        <template v-if="$slots.footer" #footer>
          <slot name="footer"></slot>
        </template>
      </Modal>
    </div>
  </teleport>
</template>

<script lang="ts" setup>
import { ref, watch, nextTick } from 'vue';
import { useRoute } from 'vue-router';
import { modalProps } from 'ant-design-vue/es/modal/Modal';
import { CloseOutlined, FullscreenOutlined, FullscreenExitOutlined } from '@ant-design/icons-vue';
import { useVModel } from '@vueuse/core';
import { throttle, omit } from 'lodash-es';
import { Modal, Space } from 'ant-design-vue';

const props = defineProps({
  ...modalProps(),
  fullscreen: {
    type: Boolean,
    default: false,
  },
  getContainer: {
    type: Function,
    default: () => document.body,
  },
});

const emit = defineEmits(['update:visible', 'update:fullscreen', 'ok', 'cancel']);

const route = useRoute();
const visibleModel = useVModel(props, 'visible');
const fullscreenModel = ref(props.fullscreen);
const innerWidth = ref('');

const cursorStyle = {
  top: 'n-resize',
  left: 'w-resize',
  right: 'e-resize',
  bottom: 's-resize',
  topLeft: 'nw-resize',
  topright: 'ne-resize',
  bottomLeft: 'sw-resize',
  bottomRight: 'se-resize',
  auto: 'auto',
} as const;

// 是否已经初始化过了
let inited = false;
const modalWrapRef = ref<HTMLDivElement>();

const closeModal = () => {
  visibleModel.value = false;
  emit('cancel');
};

// 居中弹窗
const centerModal = async () => {
  await nextTick();
  const modalEl = modalWrapRef.value?.querySelector<HTMLDivElement>('.ant-modal');

  if (modalEl && modalEl.getBoundingClientRect().left < 1) {
    modalEl.style.left = `${(document.documentElement.clientWidth - modalEl.offsetWidth) / 2}px`;
  }
};

const restore = async () => {
  fullscreenModel.value = false;
  centerModal();
};

const registerDragTitle = (dragEl: HTMLDivElement, handleEl: HTMLDivElement) => {
  handleEl.style.cursor = 'move';
  handleEl.onmousedown = throttle((e: MouseEvent) => {
    if (fullscreenModel.value) return;
    document.body.style.userSelect = 'none';
    const disX = e.clientX - dragEl.getBoundingClientRect().left;
    const disY = e.clientY - dragEl.getBoundingClientRect().top;
    const mousemove = (event: MouseEvent) => {
      if (fullscreenModel.value) return;
      let iL = event.clientX - disX;
      let iT = event.clientY - disY;
      const maxL = document.documentElement.clientWidth - dragEl.offsetWidth;
      const maxT = document.documentElement.clientHeight - dragEl.offsetHeight;

      iL <= 0 && (iL = 0);
      iT <= 0 && (iT = 0);
      iL >= maxL && (iL = maxL);
      iT >= maxT && (iT = maxT);

      dragEl.style.left = `${iL}px`;
      dragEl.style.top = `${iT}px`;
    };
    const mouseup = () => {
      document.removeEventListener('mousemove', mousemove);
      document.removeEventListener('mouseup', mouseup);
      document.body.style.userSelect = 'auto';
    };

    document.addEventListener('mousemove', mousemove);
    document.addEventListener('mouseup', mouseup);
  }, 20);
};

const initDrag = async () => {
  await nextTick();
  const modalWrapRefEl = modalWrapRef.value!;
  const modalWrapEl = modalWrapRefEl.querySelector<HTMLDivElement>('.ant-modal-wrap');
  const modalEl = modalWrapRefEl.querySelector<HTMLDivElement>('.ant-modal');
  if (modalWrapEl && modalEl) {
    centerModal();
    const headerEl = modalEl.querySelector<HTMLDivElement>('.ant-modal-header');
    headerEl && registerDragTitle(modalEl, headerEl);

    modalWrapEl.onmousemove = throttle((event: MouseEvent) => {
      if (fullscreenModel.value) return;
      const left = event.clientX - modalEl.offsetLeft;
      const top = event.clientY - modalEl.offsetTop;
      const right = event.clientX - modalEl.offsetWidth - modalEl.offsetLeft;
      const bottom = event.clientY - modalEl.offsetHeight - modalEl.offsetTop;
      const isLeft = left <= 0 && left > -8;
      const isTop = top < 5 && top > -8;
      const isRight = right >= 0 && right < 8;
      const isBottom = bottom > -5 && bottom < 8;
      // 向左
      if (isLeft && top > 5 && bottom < -5) {
        modalWrapEl.style.cursor = cursorStyle.left;
        // 向上
      } else if (isTop && left > 5 && right < -5) {
        modalWrapEl.style.cursor = cursorStyle.top;
        // 向右
      } else if (isRight && top > 5 && bottom < -5) {
        modalWrapEl.style.cursor = cursorStyle.right;
        // 向下
      } else if (isBottom && left > 5 && right < -5) {
        modalWrapEl.style.cursor = cursorStyle.bottom;
        // 左上角
      } else if (left > -8 && left <= 5 && top <= 5 && top > -8) {
        modalWrapEl.style.cursor = cursorStyle.topLeft;
        // 左下角
      } else if (left > -8 && left <= 5 && bottom <= 5 && bottom > -8) {
        modalWrapEl.style.cursor = cursorStyle.bottomLeft;
        // 右上角
      } else if (right < 8 && right >= -5 && top <= 5 && top > -8) {
        modalWrapEl.style.cursor = cursorStyle.topright;
        // 右下角
      } else if (right < 8 && right >= -5 && bottom <= 5 && bottom > -8) {
        modalWrapEl.style.cursor = cursorStyle.bottomRight;
      } else {
        modalWrapEl.style.cursor = cursorStyle.auto;
      }
    }, 20);
    modalWrapEl.onmousedown = (e: MouseEvent) => {
      if (fullscreenModel.value) return;
      const {
        top: iParentTop,
        bottom: iParentBottom,
        left: iParentLeft,
        right: iParentRight,
      } = modalEl.getBoundingClientRect();
      const disX = e.clientX - iParentLeft;
      const disY = e.clientY - iParentTop;
      const iParentWidth = modalEl.offsetWidth;
      const iParentHeight = modalEl.offsetHeight;

      const cursor = modalWrapEl.style.cursor;

      const mousemove = throttle((event: MouseEvent) => {
        if (fullscreenModel.value) return;
        if (cursor !== cursorStyle.auto) {
          document.body.style.userSelect = 'none';
        }
        const mLeft = `${Math.max(0, event.clientX - disX)}px`;
        const mTop = `${Math.max(0, event.clientY - disY)}px`;
        const mLeftWidth = `${Math.min(
          iParentRight,
          iParentWidth + iParentLeft - event.clientX,
        )}px`;
        const mRightWidth = `${Math.min(
          window.innerWidth - iParentLeft,
          event.clientX - iParentLeft,
        )}px`;
        const mTopHeight = `${Math.min(
          iParentBottom,
          iParentHeight + iParentTop - event.clientY,
        )}px`;
        const mBottomHeight = `${Math.min(
          window.innerHeight - iParentTop,
          event.clientY - iParentTop,
        )}px`;

        // 向左边拖拽
        if (cursor === cursorStyle.left) {
          modalEl.style.left = mLeft;
          modalEl.style.width = mLeftWidth;
          // 向上边拖拽
        } else if (cursor === cursorStyle.top) {
          modalEl.style.top = mTop;
          modalEl.style.height = mTopHeight;
          // 向右边拖拽
        } else if (cursor === cursorStyle.right) {
          modalEl.style.width = mRightWidth;
          // 向下拖拽
        } else if (cursor === cursorStyle.bottom) {
          modalEl.style.height = mBottomHeight;
          // 左上角拖拽
        } else if (cursor === cursorStyle.topLeft) {
          modalEl.style.left = mLeft;
          modalEl.style.top = mTop;
          modalEl.style.height = mTopHeight;
          modalEl.style.width = mLeftWidth;
          // 右上角拖拽
        } else if (cursor === cursorStyle.topright) {
          modalEl.style.top = mTop;
          modalEl.style.width = mRightWidth;
          modalEl.style.height = mTopHeight;
          // 左下角拖拽
        } else if (cursor === cursorStyle.bottomLeft) {
          modalEl.style.left = mLeft;
          modalEl.style.width = mLeftWidth;
          modalEl.style.height = mBottomHeight;
          // 右下角拖拽
        } else if (cursor === cursorStyle.bottomRight) {
          modalEl.style.width = mRightWidth;
          modalEl.style.height = mBottomHeight;
        }
        innerWidth.value = modalEl.style.width;
      }, 20);

      const mouseup = () => {
        document.removeEventListener('mousemove', mousemove);
        document.removeEventListener('mouseup', mouseup);
        document.body.style.userSelect = 'auto';
        modalWrapEl.style.cursor = cursorStyle.auto;
      };

      document.addEventListener('mousemove', mousemove);
      document.addEventListener('mouseup', mouseup);
    };
  }
  inited = true;
};

watch(visibleModel, async (val) => {
  if ((val && Object.is(inited, false)) || props.destroyOnClose) {
    initDrag();
  }
});

watch(() => route.fullPath, closeModal);
</script>

<style lang="less">
.draggable-modal {
  &.fullscreen {
    .ant-modal {
      top: 0 !important;
      right: 0 !important;
      bottom: 0 !important;
      left: 0 !important;
      width: 100% !important;
      height: 100% !important;
      max-width: 100vw !important;
    }

    .ant-modal-content {
      width: 100% !important;
      height: 100% !important;
    }
  }

  .ant-modal {
    position: relative;
    padding: 0;
    min-height: 200px;
    min-width: 200px;

    .ant-modal-close {
      top: 6px;
      right: 6px;

      &:hover,
      &:focus {
        color: rgba(0, 0, 0, 0.45);
      }

      .ant-space-item:hover .anticon,
      .ant-space-item:focus .anticon {
        color: rgba(0, 0, 0, 0.75);
        text-decoration: none;
      }

      .ant-modal-close-x {
        width: 50px;
        height: 50px;
        line-height: 44px;

        .ant-space {
          width: 100%;
          height: 100%;
        }
      }
    }

    .ant-modal-content {
      /* width: ~'v-bind("props.width")px'; */
      display: flex;
      flex-direction: column;
      width: 100%;
      height: 100%;
      min-height: 200px;
      min-width: 200px;
      overflow: hidden;

      .ant-modal-body {
        flex: auto;
        overflow: auto;
        height: 100%;
      }
    }
  }
}
</style>
